#include <file_cache.h>
#include <stdlib.h>
#include <string.h>
#include <delete_timer.h>
#include <pthread.h>
#include <stdio.h>
#include <errno.h>

struct file_entry_t* create_entry( char* path, int handle, struct flache_vol_t* vol )
{
    struct file_entry_t* entry = (struct file_entry_t*)malloc( sizeof( struct file_entry_t ) );

    if ( NULL != entry )
    {
        int pathLength = strlen( path );
        entry->path = (char*)malloc( pathLength + 1 );
        strcpy( entry->path, path );

        int flashLength = strlen( vol->flash_path );
        entry->flash_path = (char*)malloc( flashLength + pathLength + 1 );
        sprintf( entry->flash_path, "%s%s", vol->flash_path, path );

        int serverLength = strlen( vol->server_path );
        entry->server_path = (char*)malloc( serverLength + pathLength + 1 );
        sprintf( entry->server_path, "%s%s", vol->server_path, path );

        entry->handles       = NULL;
        entry->handle_length = 0;
        entry->handle_count  = 0;
        entry->dirty         = FALSE;
        entry->time_released = 0;

        pthread_mutexattr_init( &entry->lock_attrib );
        pthread_mutexattr_settype( &entry->lock_attrib, PTHREAD_MUTEX_RECURSIVE_NP );

        pthread_mutex_init( &entry->lock, &entry->lock_attrib );

        add_handle( entry, handle );
    }

    return entry;
}

void _free_entry( struct file_entry_t* entry )
{
    free( entry->path );
    free( entry->server_path );
    free( entry->flash_path );
    free( entry->handles );
    
    unlock_entry( entry );
    pthread_mutex_destroy( &entry->lock );
    pthread_mutexattr_destroy( &entry->lock_attrib );

    free( entry );
}

void add_handle( struct file_entry_t* entry, int handle )
{
    const int SIZE_INCREMENT = 100;

    if ( NULL == entry )
        return;

    lock_entry( entry );

    // if the list is too small, make it bigger
    if ( entry->handle_count == entry->handle_length )
    {
        int new_length = entry->handle_length + SIZE_INCREMENT;
        int* new_handles = (int*)malloc( new_length * sizeof(int) );
        
        // now, copy the old ones into the new one!
        int i;
        for ( i = 0; i < entry->handle_count; i++ )
            new_handles[i] = entry->handles[i];

        // swap the old list out for the new list and free old
        int* old_handles = entry->handles;
        entry->handles = new_handles;
        if ( entry->handle_length > 0 )
            free( old_handles );

        entry->handle_length = new_length;
    }

    entry->handles[entry->handle_count] = handle;
    entry->handle_count++;

    unlock_entry( entry );
}

void remove_handle( struct file_entry_t* entry, int handle )
{
    if ( NULL == entry )
        return;

    lock_entry( entry );
    
    int i;
    for ( i = entry->handle_count - 1; i >= 0; i-- )
    {
        if ( entry->handles[i] == handle )
        {
            int j;
            for ( j = i; j < entry->handle_count - 1; j++ )
                entry->handles[j] = entry->handles[j+1];

            entry->handle_count--;
            break;
        }
    }

    if ( 0 == entry->handle_count )
        entry->time_released = clock();

    unlock_entry( entry );
}

int contains_handle( struct file_entry_t* entry, int handle )
{
    lock_entry( entry );

    int result = FALSE;

    int i;
    for( i = 0; i < entry->handle_count && !result; i++ )
        result = ( entry->handles[i] == handle );

    unlock_entry( entry );

    return result;
}

void rename_entry( struct file_entry_t* file, const char* new_path )
{
    lock_entry( file );
    
    int old_length = strlen( file->path );
    int new_length = strlen( new_path );
    int server_path_length = strlen( file->server_path ) - old_length;
    int flash_path_length = strlen( file->flash_path ) - old_length;

    char server_path[server_path_length + 1];
    strncpy( server_path, file->server_path, server_path_length );
    server_path[server_path_length] = 0;

    char flash_path[flash_path_length + 1];
    strncpy( flash_path, file->flash_path, flash_path_length );
    flash_path[flash_path_length] = 0;

    free( file->path );
    free( file->server_path );
    free( file->flash_path );

    file->path = (char*)malloc( strlen( new_path ) + 1 );
    strcpy( file->path, new_path );

    file->flash_path = (char*)malloc( flash_path_length + new_length + 1 );
    sprintf( file->flash_path, "%s%s", flash_path, new_path );

    file->server_path = (char*)malloc( server_path_length + new_length + 1 );
    sprintf( file->server_path, "%s%s", server_path, new_path );

    unlock_entry( file );
}

void lock_entry( struct file_entry_t* entry )
{
    pthread_mutex_lock ( &entry->lock );
}

void unlock_entry( struct file_entry_t* entry )
{
    pthread_mutex_unlock ( &entry->lock );
}

struct file_entry_list_t* create_entry_list()
{
    struct file_entry_list_t* list;
    list = (struct file_entry_list_t*)malloc( sizeof(struct file_entry_list_t) );
    
    list->entries    = NULL;
    list->file_count = 0;
    list->list_size  = 0;

    pthread_mutexattr_init( &list->lock_attrib );
    pthread_mutexattr_settype( &list->lock_attrib, PTHREAD_MUTEX_RECURSIVE_NP );

    pthread_mutex_init( &list->lock, &list->lock_attrib );
    
    return list;
}

void free_entry_list( struct file_entry_list_t* list )
{
    if ( NULL == list )
        return;

    // first, iterate through the files and free them
    int i; 
    for ( i = list->file_count - 1; i >= 0; i-- )
    {
        lock_entry( list->entries[i] );
        _free_entry( list->entries[i] );
    }

    free( list->entries );

    unlock_list( list );

    pthread_mutex_destroy( &list->lock );
    pthread_mutexattr_destroy( &list->lock_attrib );

    free( list );
}

void lock_list( struct file_entry_list_t* list ) { pthread_mutex_lock( &list->lock ); }
void unlock_list( struct file_entry_list_t* list ) { pthread_mutex_unlock( &list->lock ); }

void add_file( struct file_entry_list_t* list, struct file_entry_t* entry )
{
    const int SIZE_INCREMENT = 100;

    if ( NULL == list )
        return;

    lock_list( list );

    if ( list->list_size == list->file_count )
    {
        int new_length = list->list_size + SIZE_INCREMENT;
        struct file_entry_t** new_entries;
        new_entries = (struct file_entry_t**)malloc( new_length * sizeof(struct file_entry_t*) );
        
        // now, copy the old ones into the new list!
        int i;
        for ( i = 0; i < list->file_count; i++ )
            new_entries[i] = list->entries[i];

        // swap the old list out for the new list and free old
        struct file_entry_t** old_entries = list->entries;
        list->entries = new_entries;
        if ( list->list_size > 0 )
            free( old_entries );

        list->list_size = new_length;
    }

    list->entries[list->file_count] = entry;
    list->file_count++;

    unlock_list( list );
}

void remove_file( struct file_entry_list_t* list, struct file_entry_t* entry )
{
    if ( NULL == list )
        return;

    lock_list( list );
    
    int i;
    for ( i = list->file_count - 1; i >= 0; i-- )
    {
        if ( list->entries[i] == entry )
        {
            int j;
            for ( j = i; j < list->file_count - 1; j++ )
                list->entries[j] = list->entries[j+1];

            list->file_count--;
            break;
        }
    }

    unlock_list( list );
}

struct file_entry_t* get_file( struct file_entry_list_t* list, char* path )
{
    if ( NULL == list )
        return NULL;

    struct file_entry_t* found_file = NULL;
    struct file_entry_t* curr_file = NULL;

    lock_list( list );

    int i; 
    for ( i = 0; i < list->file_count; i++ )
    {
        lock_entry( list->entries[i] );

        curr_file = list->entries[i];

        // if we've found the file, then break out and return it
        // otherwise, unlock it.
        if ( 0 == strcmp( path, curr_file->path ) )
        {
            found_file = curr_file;
            break;
        }
        else
            unlock_entry( curr_file );
    }   

    unlock_list( list );

    return found_file;
}

/* prints a listing of file_list to argument file */
void print_list( FILE* file, struct file_entry_list_t* file_list )
{
    lock_list( file_list );

    int i;
    for ( i = 0; i < file_list->file_count; i++ )
        fprintf( file, "%s\n", (file_list->entries[i])->path );

    unlock_list( file_list );
}
